<template>
  <view :="fv.rootProps">
    <view :class="[variables.ns.e('mask')]" v-if="state.showContent" @click="visibleChange()"></view>
    <view :class="[variables.ns.e('wrapper')]" @click="visibleChange">
      <view :class="[variables.ns.e('placeholder')]" v-if="fv.showPlaceholder">{{ fv.placeholder }}</view>
      <view :class="[variables.ns.e('selection')]">
        <view :class="[variables.ns.e('selection-item')]" @click.stop="config.clickRemoveItem(item)" v-for="item in fv.itemList"
          :key="String(item.v)">
          <view :class="[variables.ns.e('select-item-text')]">
            <slot name="label" :="item">{{ item.label ?? item.value }}</slot>
          </view>
          <view class="iconfont icon-close" :class="[variables.ns.e('select-item-close')]" v-if="multiple"></view>
        </view>
        <view :class="[variables.ns.e('selection-item')]" v-if="fv.showOptionCount">+ {{ fv.optionCount }}</view>
        <input v-model="state.searchText" :="fv.inputProps" v-if="fv.showInput"
          @click.stop="config.clickInput()"></input>
      </view>
      <view :class="[variables.ns.e('suffix')]" @click.stop="config.clickIcon()">
        <slot name="suffix" :="{ isArrow: fv.isArrow }">
          <view :="fv.suffixProps"></view>
        </slot>
      </view>
    </view> 
    <Paging @touchmove.stop.prevent :="fv.pagingProps" ref="pagingRef">
      <template v-for="slotName in Object.keys($slots)" v-slot:[slotName]="scoped">
        <slot :name="slotName" :="scoped"></slot>
      </template>
    </Paging>
  </view>
</template>

<script lang="ts" setup>
import { isEmpty } from '@/utils'
import { SelectNS, variables } from '.'
import Paging from '@/components/paging/index.vue'
import { computed, onMounted, provide, reactive, ref, watch } from 'vue'
import { t } from '@/locale'
import { debounce, get, isObject, merge } from 'lodash'
import { OptionNS, PagingNS } from '@/components'
import { CatchError } from '@/utils/catch-error'

defineOptions({
  name: SelectNS.name
})
const props = defineProps(SelectNS.props)
const emits = defineEmits(SelectNS.emits)
const state = reactive({
  showContent: false,
  searchText: '',
  childMap: new Map<Exclude<OptionNS.Props['value'], object>, OptionNS.Props>(),
  optionList: Array<OptionNS.Props & { v: Exclude<OptionNS.Props['value'], object> }>()
})
const config = {
  remoteCe: new CatchError(),
  clickRemoveItem(param: typeof state.optionList[number]) {
    if (!props.multiple) return visibleChange()
    removeItem(param)
  },
  clickInput() {
    if (state.showContent) return
    visibleChange()
  },
  clickIcon() {
    if (fv.value.isArrow) return visibleChange()
    clear()
  },
  inputDebouce() {
    const p = pagingRef.value?.pagingRef
    const reload = () => p?.reload()
    if (isEmpty(props.debouce)) return reload
    if (typeof props.debouce === 'number') return debounce(reload, props.debouce)
    return props.debouce({ pagingRef: p })
  },
  getValue: (v?: OptionNS.Props['value']) => isObject(v) ? get(v, props.valueKey!) : v
}
const pagingRef = ref<PagingNS.Instance>()
const fv = computed(() => {
  const rootProps = {
    class: [
      variables.ns.b,
      variables.ns.s('disabled', props.disabled),
      variables.ns.s('fit-width', props.fitInputWidth),
      variables.ns.s('show', state.showContent),
      variables.ns.s('multiple', props.multiple),
      variables.ns.s('collapse-tag', props.collapseTags),
    ],
  }
  const pagingProps = merge(<PagingNS.Props>{
    fixed: false,
    auto: false,
    class: [variables.ns.e('content')],
    lowerThreshold: '60rpx',
    autoShowBackToTop: false,
    refresherEnabled: Boolean(props.remoteMethod),
  }, props.pagingProps, <PagingNS.Props>{
    onQuery: (pageNo, pageSize) => {
      const p = pagingRef.value?.pagingRef
      config.remoteCe.run = async () => {
        const list = await props.remoteMethod?.({
          pagingRef: p,
          text: state.searchText,
          pageNo, pageSize,
        })
        if (!Array.isArray(list)) return
        p?.complete(list)
      }
      config.remoteCe.fail = () => p?.complete(false)
    },
    hideEmptyView: !props.remoteMethod
  })
  const valueEmpty = isEmpty(props.modelValue) || props.multiple && !(props.modelValue as any[])?.length
  const isArrow = !props.clearable || valueEmpty
  const suffixProps = {
    class: ['iconfont', isArrow ? 'icon-arrow-down' : 'icon-close'],
  }
  const showInput = props.remote
  const placeholder = valueEmpty ? (props.placeholder ?? t('components.select.PleaseSelect')) : ''
  const showPlaceholder = valueEmpty && !props.remote
  const optionCount = Math.max(state.optionList.length - 1, 0)
  const showOptionCount = props.multiple && props.collapseTags && optionCount
  const inputProps = {
    type: 'text',
    maxlength: -1,
    class: [variables.ns.e('selection-input')],
    placeholder,
    onInput: config.inputDebouce(),
    onBlur(e: Event) {
      emits('blur', e)
    },
    onFocus(e: Event) {
      pagingRef.value?.pagingRef?.reload()
      emits('focus', e)
    }
  }
  return {
    rootProps,
    pagingProps,
    suffixProps,
    isArrow,
    placeholder,
    showPlaceholder,
    showInput,
    optionCount,
    showOptionCount,
    itemList: props.collapseTags ? state.optionList.slice(0, 1) : state.optionList,
    inputProps
  }
})
const provideContext: variables.ProvideContext = {
  isActive(param) {
    const equals = (mv?: typeof param.value) => config.getValue(param.value) === config.getValue(mv)

    state.childMap.set(config.getValue(param.value), param)

    if (props.multiple) return [...props.modelValue as any[]].some(equals)

    return equals(props.modelValue)
  },
  change(param) {
    if (param.disabled) return
    if (this.isActive(param)) {
      if (props.multiple) return removeItem(config.getValue(param.value))
      return visibleChange()
    }
    const value = param.value
    if (props.multiple) {
      const list = [...props.modelValue as any[]]
      const limit = props.multipleLimit

      if (limit && list.length >= limit) return emits('exceed', param, list)

      list.push(value)
      emits('update:modelValue', list)
      emits('change', list)
      return
    }
    emits('update:modelValue', value)
    emits('change', value)
    visibleChange()
  },
  props
}
provide(variables.provideName, provideContext)

function visibleChange() {
  state.showContent = !state.showContent

  if (!state.showContent) state.searchText = ''

  emits('visibleChange', state.showContent)
}
function clear() {
  const value = props.multiple ? [] : undefined
  emits('update:modelValue', value)
  emits('change', value)
  emits('clear')
}
function removeItem(item: typeof state.optionList[number]) {
  if (!Array.isArray(props.modelValue)) return
  const list = [...props.modelValue]
  const index = list.findIndex(o => config.getValue(o) === item.v)

  list.splice(index, 1)[0]

  emits('update:modelValue', list)
  emits('change', list)
  emits('removeItem', item)
}
function setOptions() {
  state.optionList = []
  const setItem = (p: OptionNS.Props['value']) => {
    const v = config.getValue(p)
    state.optionList.push({ ...state.childMap.get(v)!, v })
  }
  if (isEmpty(props.modelValue)) return
  if (props.multiple) return (props.modelValue as any[]).forEach(setItem)
  setItem(props.modelValue)
}

watch(() => props.modelValue, setOptions)
onMounted(setOptions)
</script>